package org.concepts.java.classloader.hello;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * HelloClassLoader to show how to implement a simple classloader. This is a
 * very simplistic classloader. It lets the user specify a directory from which
 * classes will be loaded
 * <p/>
 * The classloader must implement the findClass method. This method is used as
 * part of {@link ClassLoader#loadClass(String)}.
 *<p/>
 * Some of the code is taken from
 * http://java.sun.com/developer/onlineTraining/Security
 * /Fundamentals/magercises/ClassLoader/solution/FileClassLoader.java
 * 
 * 
 * @author vvvv
 * 
 */
public class FileClassLoader extends ClassLoader {

  private final File classDir;

  private ConcurrentMap<String, Integer> loadCount = null;

  public FileClassLoader(File classDir) {
    this.classDir = classDir;
    this.loadCount = new ConcurrentHashMap<String, Integer>();
  }

  /**
   * Overriding findClass method to find, load, and define the class.
   * 
   * @throws ClassNotFoundException
   *           Thrown if class is not found or if there's any error loading the
   *           class.
   */
  public Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] b = loadClassData(name);
    Class<?> definedClass= defineClass(name, b, 0, b.length);
    this.loadCount.putIfAbsent(name, 0);
    this.loadCount.put(name, this.loadCount.get(name) + 1);
    return definedClass;
  }

  /**
   * Returns the number of times that the class is loaded. This method is
   * only added for testing and training.
   * @param name
   * @return
   */
  public int loadCount(String name) {
    Integer count = this.loadCount.get(name); 
    return (count == null) ? 0 : count; 
  }
  

  /**
   * Loads the class with the specified name.
   * 
   * @param name
   *          name of the class.
   * @throws ClassNotFoundException
   *           Thrown if class is not found. It's thrown if null is passed in.
   * @return
   */
  private byte[] loadClassData(String name) throws ClassNotFoundException {
    if (name == null) {
      throw new ClassNotFoundException("class [" + name + "] not found. dir ["
          + this.classDir + "]");
    }

    String filename = name.replace('.', File.separatorChar) + ".class";
    File f = new File(this.classDir.getPath(), filename);
    int size = (int) f.length();
    byte buff[] = new byte[size];

    FileInputStream fis;
    try {
      fis = new FileInputStream(f);
    } catch (FileNotFoundException e) {
      throw new ClassNotFoundException("class [" + name + "] not found in ["
          + this.classDir + "]", e);
    }

    DataInputStream dis = new DataInputStream(fis);
    try {
      dis.readFully(buff);
      dis.close();
    } catch (Exception e) {
      throw new ClassNotFoundException("Error reading  class [" + name
          + "] from [" + this.classDir + "]", e);
    }

    return buff;
  }

}
